﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Common.Locator;
using Fuse8.DomainFramework.BusinessService;

using Fuse8.ContentManagementFramework.SystemFacade;
using Fuse8.ContentManagementFramework.Domain;
using Fuse8.ContentManagementFramework.BizFacade.Services;
using Fuse8.ContentManagementFramework.Domain.Commands;
using Fuse8.ContentManagementFramework.Domain.Commands.CommandParameters;
using Fuse8.ContentManagementFramework.SystemFacade.Common;
using Fuse8.ContentManagementFramework.SystemFacade.Utilities;

namespace Fuse8.ContentManagementFramework.Web.ApplicationRuntime
{
    public sealed class FileUploader
    {
        public const string FileUploadChunkLengthKey = "FileUploadChunkLength";
        public const string FileUploadChunkLinkedFileNameKey = "FileUploadChunkLinkedFileName";

        private CmsMediaFileService _mediaFileService = new CmsMediaFileService();
        private CmsMimeTypeService _mimeTypeService = new CmsMimeTypeService();

        private int _fileLength;
        private string _originalFileName;
        private string _linkedFileName;
        private string _pathToFiles;
        private string _fileContentType;
        private HttpPostedFileBase _postedFile;
        private HttpSessionStateBase _session;

        private string _sessionFileNameKey;
        private string _sessionFileLengthKey;

        public FileUploader(string fileName, HttpFileCollectionBase postedFiles, HttpSessionStateBase session)
        {
            Guard.ArgumentNotEmpty(fileName, "fileName");
            Guard.ArgumentNotNull(postedFiles, "postedFiles");
            Guard.ArgumentNotNull(session, "session");

            var currentContext = DependencyServiceLocator.Current.GetInstance<ICmsContext>();

            _postedFile = postedFiles[0];

            _originalFileName = _postedFile.FileName;
            _linkedFileName = string.Empty;
            _fileContentType = _postedFile.ContentType;

            _pathToFiles = currentContext.MapPath(CmsConfiguration.AssetUploadPath);

            _session = session;
            _sessionFileNameKey = string.Format("{0} for {1}", FileUploadChunkLinkedFileNameKey, _originalFileName);
        }

        public CmsMediaFile Upload(int chunk, int chunks, int? exitingFileId = null)
        {
            CmsMediaFile result = null;

            _linkedFileName = GetLinkedFileName(chunk, chunks);
            _fileLength = GetFileLength(chunk, chunks);

            if (chunks > 1)
            {
                UploadChunkedFile(chunk, _linkedFileName, _pathToFiles, _postedFile);
            }
            else
            {
                SaveToFile(_postedFile, Path.Combine(_pathToFiles, _linkedFileName));
            }

            if (_fileLength > 0)
            {
                var imageDimensions = ImageResizer.GetImageDimension(Path.Combine(_pathToFiles, _linkedFileName));
                
                CmsMimeType mimeType = null;
                using (var transaction = new BusinessServiceTransaction<CmsMediaFile>().With<CmsMimeType>())
                {
                    mimeType = _mimeTypeService.GetByContentType(_fileContentType);
                    if (mimeType == null)
                    {
                        mimeType = new CmsMimeType()
                                       {
                                           ContentType = _fileContentType,
                                           IsImage = false,
                                           MimeTypeName = _fileContentType
                                       };
                       _mimeTypeService.Add(mimeType);
                    }

                    result = _mediaFileService.UploadToMediaFile(
                        _originalFileName,
                        _linkedFileName,
                        mimeType.Id,
                        _fileLength,
                        imageDimensions.HasValue ? imageDimensions.Value.Height : new int?(),
                        imageDimensions.HasValue ? imageDimensions.Value.Width : new int?(),
                        exitingFileId);

                    result.MimeType = mimeType;

                    transaction.Commit();
                }
            }

            return result;
        }

        private string GetLinkedFileName(int chunk, int chunks)
        {
            string result = FileNameGenerator.GenerateGuidName(Path.GetExtension(_originalFileName));

            if (chunks > 1)
            {
                if (_session[_sessionFileNameKey] == null)
                {
                    if (chunk == 0)
                    {
                        _session[_sessionFileNameKey] = result;
                    }
                    else
                    {
                        throw new HttpException(500, "Session expired");
                    }
                }
                else
                {
                    result = _session[_sessionFileNameKey].ToString();
                }
            }

            return result;
        }

        private int GetFileLength(int chunk, int chunks)
        {
            int result = _postedFile.ContentLength;

            if (chunks > 1)
            {
                _sessionFileLengthKey = string.Format("{0} for {1}", FileUploadChunkLengthKey, _linkedFileName);

                if (_session[_sessionFileLengthKey] == null)
                {
                    if (chunk == 0)
                    {
                        _session[_sessionFileLengthKey] = result;
                        result = 0;
                    }
                    else
                    {
                        throw new HttpException(500, "Session expired");
                    }
                }
                else
                {
                    if (chunk < chunks - 1)
                    {
                        _session[_sessionFileLengthKey] = (int)_session[_sessionFileLengthKey] + result;
                        result = 0;
                    }
                    else
                    {
                        result += (int)_session[_sessionFileLengthKey];

                        CleanUp();
                    }
                }
            }

            return result;
        }

        private void CleanUp()
        {
            if (_session != null)
            {
                if (!string.IsNullOrEmpty(_sessionFileLengthKey))
                {
                    _session[_sessionFileLengthKey] = null;
                }
                if (!string.IsNullOrEmpty(_sessionFileNameKey))
                {
                    _session[_sessionFileNameKey] = null;
                }
            }
        }


        private static void UploadChunkedFile(int chunk, string linkedFileName, string pathToFiles, HttpPostedFileBase postedFile)
        {
            if (chunk == 0)
            {
                SaveToFile(postedFile, Path.Combine(pathToFiles, linkedFileName));
            }
            else
            {
                AppendToFile(postedFile, Path.Combine(pathToFiles, linkedFileName));
            }
        }

        #region File operation

        private static void SaveToFile(HttpPostedFileBase postedFile, string filePath)
        {
            postedFile.SaveAs(filePath);
        }

        private static void AppendToFile(HttpPostedFileBase postedFile, string filePath)
        {
            using (var fs = new FileStream(filePath, FileMode.Append))
            {
                var buffer = new byte[postedFile.InputStream.Length];

                postedFile.InputStream.Read(buffer, 0, buffer.Length);

                fs.Write(buffer, 0, buffer.Length);
            }
        }

        #endregion
    }
}
